#include <iostream>
#include <map>
#include <cstring>
#include <cstdlib>
#include <fstream>
#include <cstdlib>

//Application Headers
#include "Instruction.h"
#include "Math.h"
#include "Def.h"
#include "Table.h"
#include "Utility.h"

using namespace std;

//Array to hold the memory&Register values
int *Memory = NULL;
int *Register = NULL;
int memoryLimit;
int InstLimit;

//Map to hold the memory location and
//corresponding Instruction
unordered_map<int, Instruction*> instructionMap;
unordered_map<int, Instruction*>::iterator it;

//Map to hold the memory location and
//corresponding Instruction
unordered_map<int, char*> memoryMap;
unordered_map<int, char*>::iterator memoryIt;

//Program Counter
int PC;

int main(int argc, char *argv[]) {

	if (argc < 2 || argc > 2) {
		cerr << "Invalid input to program" << endl;
		cout << "Run as: MIPSsim <input file name>" << endl;
		exit(1);
	}

	//Always call this functoin before opening the file
	ValidateInputFile(argv[1]);

	//Open the input file and validate
	FILE *file = fopen(argv[1], "r");
	if (file == NULL) {
		cerr << "Unable to open the input file: " << argv[1] << endl;
		exit(1);
	}

	Table table;
	Instruction *instruction;

	//Remove any previous files left
	remove(SIMULATION_FILE_NAME);
	remove(DISASSEMBLY_FILE_NAME);

	char *ch;
	ch = (char *) malloc(sizeof(char) * 80);
	size_t len;

	Memory = (int *) malloc(sizeof(int) * 10000);
	if (Memory == NULL) {
		cout << "Unable to allocate for memory array" << endl;
		exit(1);
	}

	Register = (int*) malloc(sizeof(int) * 32);
	if (Register == NULL) {
		cout << "Unable to allocate for memory array" << endl;
		exit(1);
	}

	memset(Memory, 0, sizeof(int) * 10000);
	memset(Register, 0, sizeof(int) * 32);

	int lineCount = 0;
	int memoryLocation;

	//While loop to set the momory locations
	while (getline(&ch, &len, file) != EOF) {
		ch[32] = '\0';
		//Check if the instruction is break
		if (strncmp(ch, "010101", 6) == 0) {
			while (getline(&ch, &len, file) != EOF) {
				char *temp;
				temp = (char*) malloc(sizeof(int) * 33);

				lineCount++;
				ch[32] = '\0';
				strcpy(temp, ch);

				memoryLocation = STARTADDR + 4 * lineCount;
				Memory[memoryLocation] = BinaryToDecimal((const char*) ch);
				memoryMap[memoryLocation] = temp;
				//cout << "memory: " << Memory[memoryLocation];
				memoryLimit = memoryLocation;
			}
		}
		lineCount++;
	}

	//Go back to the begining of the file
	fseek(file, 0, SEEK_SET);

	char *nextInstruction;
	nextInstruction = (char*) malloc(100);

	//Initial value of PC always starts from 256/STARTADDR
	PC = STARTADDR;

	while (getline(&ch, &len, file) != EOF) {

		ch[32] = '\0';

		instruction = new Instruction(ch);

		//Mandatory to call this function
		//It will inialize few of the parameters
		instruction->Initialize();

		instructionMap[PC] = instruction;

		Instruction next = Instruction(nextInstruction);

		InstLimit = PC;
		PC += 4;

		if (instruction->isEqual("BREAK"))
			break;

	}

	//Print the disassembly code
	WriteDisassemplyToFile(DISASSEMBLY_FILE_NAME);

	//Reset the PC to begin execution
	PC = STARTADDR;
	int cycleNo = 0;
	int tempPC;

	while (1) {

		cycleNo++;

		//only used to print
		tempPC = PC;
		Instruction *tempInst;

		tempInst = instructionMap.at(PC);

		tempInst->Execute();

		WriteSimulationToFile(cycleNo, tempPC, SIMULATION_FILE_NAME);

		if (tempInst->isEqual("BREAK"))
			break;

	}

	fclose(file);

	//Free all the allocated memory
	free(nextInstruction);
	free(ch);
	free(Memory);
	free(Register);

	for (it = instructionMap.begin(); it != instructionMap.end(); it++)
		delete (it->second);

	for (memoryIt = memoryMap.begin(); memoryIt != memoryMap.end();
			memoryIt++) {
		free(memoryIt->second);
	}

	return 0;
}
